//
//  World.m
//  Box2DTest
//
//  Created by David Wagner on 19/06/2009.
//  Copyright 2009 Zygote Commando. All rights reserved.
//

#import "World.h"
#import "AAWLog.h"

#define kPhysicsHz (1.0f/30.0f)
#define kMaxPhysicsUpdateRun (5*kPhysicsHz)

#define kMaxActors (50)

#define kFilteringFactor (0.05)

#define kWallHalfThickness (100.0f)
#define kArenaHalfWidth (32.0f/2)
#define kArenaHalfHeight (48.0f/2)

// A class extension to declare private methods
@interface World ()
@property (retain) NSMutableArray* actors;

- (void) initDefaultWorld;
- (void) initWalls;

@end

// World implementation
@implementation World

@synthesize actors;

- (void) initDefaultWorld
{
	// Create the bounding box for the world.
	b2AABB worldAABB;
	
	worldAABB.lowerBound.Set( -50.0f, -50.0f );
	worldAABB.upperBound.Set(  50.0f,  50.0f );
	
	// Default gravity
	b2Vec2 gravity( 0.0f, -10.0f );
	bool doSleep = false;
	
	// Create the world object
	world = new b2World( worldAABB, gravity, doSleep );
}

- (void) initWalls
{
	b2BodyDef wallBodyDef;
	b2PolygonDef wallShapeDef;
	b2Body* wallBody;

	// Bottom and top
	wallShapeDef.SetAsBox( kArenaHalfWidth, kWallHalfThickness );

	wallBodyDef.position.Set( 0.0f, -kArenaHalfHeight - kWallHalfThickness);
	wallBody = world->CreateBody( &wallBodyDef );
	wallBody->CreateShape(&wallShapeDef);	

	wallBodyDef.position.Set( 0.0f, kArenaHalfHeight + kWallHalfThickness - 2);
	wallBody = world->CreateBody( &wallBodyDef );
	wallBody->CreateShape(&wallShapeDef);
	
	// Left and right
	wallShapeDef.SetAsBox( kWallHalfThickness, kArenaHalfHeight );

	wallBodyDef.position.Set( -kArenaHalfWidth - kWallHalfThickness, 0.0f );
	wallBody = world->CreateBody( &wallBodyDef );
	wallBody->CreateShape(&wallShapeDef);

	wallBodyDef.position.Set( kArenaHalfWidth + kWallHalfThickness, 0.0f );
	wallBody = world->CreateBody( &wallBodyDef );
	wallBody->CreateShape(&wallShapeDef);
}

- (void)createSquareActorAtX: (float)x Y: (float)y theOne: (BOOL)theOne
{
	Actor* actor = [[[Actor alloc] initWithWorld:world] autorelease];
	
	actor.theOne = theOne;
	[actor setX:x Y:y];
	[actor setLinearVelocityX:0 Y:-world->m_gravity.y*2];

	[actors addObject:actor];
	
	while (actors.count > kMaxActors) {
		for(int i = 0; i < actors.count; i++)
		{
			Actor* actor = [actors objectAtIndex:i];
			if( !actor.theOne )
			{
				[actors removeObjectAtIndex:i];
				break;
			}
		}
	}
}

- (id) init
{
	if((self = [super init]))
	{
		self.actors = [[[NSMutableArray alloc] init] autorelease];
		[self initDefaultWorld];
		[self initWalls];

		lastUpdate = [NSDate timeIntervalSinceReferenceDate];
		updateAccumulator = 0.0f;
		updateGraphics = YES;

        [[UIAccelerometer sharedAccelerometer] setUpdateInterval:(1.0f/5.0f)];
        [[UIAccelerometer sharedAccelerometer] setDelegate:self];	
	}
	
	return self;
}

- (void) dealloc
{
	delete world;
	
	self.actors = nil;
	
	[super dealloc];
}

- (NSString *) description
{
	return [NSString stringWithFormat:@"[World %p]", self ];
}

/**
 * Update the game. The looping here, which is
 * trying to keep the physics update independent
 * from the graphics update, is cheerfully
 * taken from the following excellent article:
 *
 *   http://gafferongames.com/game-physics/fix-your-timestep/
 */
- (BOOL) tick
{
	NSTimeInterval now = [NSDate timeIntervalSinceReferenceDate];
	NSTimeInterval delta = now - lastUpdate;
	lastUpdate = now;

	if( delta > kMaxPhysicsUpdateRun )
	{
		delta = kMaxPhysicsUpdateRun;
	}
	
	updateAccumulator += delta;
	
	while (updateAccumulator >= kPhysicsHz)
	{
//		NSLog( @"[%@ physics tick]", [self class] );
		world->Step( kPhysicsHz, 5 );
		updateAccumulator -= kPhysicsHz;
		updateGraphics = YES;
	}
	
	if(actors.count < 1 )
	{
		[self createSquareActorAtX:0.5 Y: 0 theOne: YES];
	}
	
	return updateGraphics;
}

- (void)draw
{
//	NSLog(@"[%@ draw]", [self class]);
	glMatrixMode(GL_PROJECTION);
	glLoadIdentity();
	glOrthof(-kArenaHalfWidth, kArenaHalfWidth, -kArenaHalfHeight, kArenaHalfHeight, -kArenaHalfWidth, kArenaHalfWidth);
	
    glMatrixMode(GL_MODELVIEW);
    glClearColor(0.5f, 0.5f, 0.5f, 1.0f);
    glClear(GL_COLOR_BUFFER_BIT);

	//[actors makeObjectsPerformSelector:@selector(draw)];
	for(Actor* actor in actors)
	{
		[actor draw];
	}
	
	updateGraphics = NO;
}

// UIAccelerometer delegate method, which delivers the latest acceleration data.
- (void)accelerometer:(UIAccelerometer *)accelerometer didAccelerate:(UIAcceleration *)acceleration 
{
	AAWLog(@"ax: %f, ay: %f",  acceleration.x,  acceleration.y);

	b2Vec2 gravity( acceleration.x * 10, acceleration.y * 10 );
	world->SetGravity(gravity);
}

@end
